package main

import (
	"fmt"
	"reflect"
)

type S struct {
	Pub int // 可导出、可寻址、可写
	pri int // 不可导出 → 即使拿到地址也写不了
}

func main() {
	fmt.Println("========= 1. 可写路径 =========")
	// 必须取地址再 Elem，才能拿到可写 reflect.Value
	s := &S{Pub: 10, pri: 20}
	v := reflect.ValueOf(s).Elem()

	pubFld := v.FieldByName("Pub")
	fmt.Printf("Pub  CanAddr=%t  CanSet=%t  旧值=%v\n", pubFld.CanAddr(), pubFld.CanSet(), pubFld.Int())
	pubFld.SetInt(99)
	fmt.Println("SetInt(99) 后 ->", pubFld.Int())

	// pri 字段虽然能拿到，但不可导出
	priFld := v.FieldByName("pri")
	fmt.Printf("pri  CanAddr=%t  CanSet=%t  旧值=%v\n", priFld.CanAddr(), priFld.CanSet(), priFld.Int())
	// priFld.SetInt(88) // 放开会 panic: reflect.Value.SetInt using value obtained using unexported field

	fmt.Println("\n========= 2. 各种 Set* 演示 =========")
	var (
		i   int     = 1
		f   float64 = 2.5
		b   bool    = true
		str string  = "gopher"
		bs  []byte  = []byte("hello")
	)
	vi := reflect.ValueOf(&i).Elem()
	vf := reflect.ValueOf(&f).Elem()
	vb := reflect.ValueOf(&b).Elem()
	vs := reflect.ValueOf(&str).Elem()
	vbs := reflect.ValueOf(&bs).Elem()

	vi.SetInt(100)
	vf.SetFloat(3.14)
	vb.SetBool(false)
	vs.SetString("world")
	vbs.SetBytes([]byte("reflect"))

	fmt.Printf("int=%d  float=%f  bool=%t  string=%s  []byte=%s\n",
		i, f, b, str, bs)

	fmt.Println("\n========= 3. 不可写场景（panic） =========")
	// 3.1 直接传值 → 副本不可写
	v3 := reflect.ValueOf(42)
	fmt.Println("v3.CanSet() =", v3.CanSet()) // false
	// v3.SetInt(0) // 放开即 panic: reflect.Value.SetInt using unaddressable value

	// 3.2 空指针
	var nilPtr *int
	v4 := reflect.ValueOf(nilPtr)
	fmt.Println("空指针 v4.IsValid() =", v4.IsValid()) // true
	// v4.Elem() 这里已经 panic: reflect.Value.Elem of zero Value
}

// 判定及获取元素的相关方法
// | 方法名              | 备注                                                         |
// | ---------------- | ---------------------------------------------------------- |
// | `Elem() Value`   | 取值指向的元素值，类似于语言层 `*` 操作。当值类型不是指针或接口时发生宕机，空指针时返回 nil 的 Value |
// | `Addr() Value`   | 对可寻址的值返回其地址，类似于语言层 `&` 操作。当值不可寻址时发生宕机                      |
// | `CanAddr() bool` | 表示值是否可寻址                                                   |
// | `CanSet() bool`  | 返回值能否被修改。要求值可寻址且是导出的字段                                     |

// 值修改相关方法
// | 方法名                   | 备注                                                                     |
// | --------------------- | ---------------------------------------------------------------------- |
// | `Set(x Value)`        | 将值设置为传入的反射值对象的值                                                        |
// | `SetInt(x int64)`     | 使用 int64 设置值。当值的类型不是 `int`、`int8`、`int16`、`int32`、`int64` 时会发生宕机       |
// | `SetUint(x uint64)`   | 使用 uint64 设置值。当值的类型不是 `uint`、`uint8`、`uint16`、`uint32`、`uint64` 时会发生宕机 |
// | `SetFloat(x float64)` | 使用 float64 设置值。当值的类型不是 `float32`、`float64` 时会发生宕机                      |
// | `SetBool(x bool)`     | 使用 bool 设置值。当值的类型不是 `bool` 时会发生宕机                                      |
// | `SetBytes(x []byte)`  | 设置字节数组 `[]byte` 值。当值的类型不是 `[]byte` 时会发生宕机                              |
// | `SetString(x string)` | 设置字符串值。当值的类型不是 `string` 时会发生宕机                                         |

